export class VBus {
  constructor() {
    function getUniqueId() {
      return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (w) => {
        const r = (Math.random() * 16) | 0;
        return (w === 'x' ? r : (r & 0x3) | 0x8).toString(16);
      });
    }
    function isEventNameValid(event) {
      return typeof event === 'string' && /^[a-z]/i.test(event);
    }
    function isEventExist(event) {
      return pool[event] && typeof pool[event] === 'object';
    }

    const pool = {};
    const fieldName = '_v_bus_id_' + Date.now().toString(36);

    this.$on = (event, handler, ctx) => {
      if (!isEventNameValid(event)) {
        throw new Error(`vBus $on: event name ${event} is not valid`);
      }
      if (typeof handler !== 'function') {
        throw new Error(`vBus $on: handler of event ${event} is required a function`);
      }
      if (!isEventExist(event)) {
        pool[event] = {};
      }
      if (!handler[fieldName]) {
        handler[fieldName] = getUniqueId();
      }
      pool[event][handler[fieldName]] = { handler, ctx };
      return handler[fieldName];
    };
    this.$off = (event, handler) => {
      if (!isEventExist(event)) {
        console.warn(`vBus $off: event name ${event} is not exist`);
        return;
      }
      if (typeof handler === 'function') {
        delete pool[event][handler[fieldName]];
        delete handler[fieldName];
        return;
      }
      if (typeof handler === 'string' && pool[event][handler]) {
        delete pool[event][handler][fieldName];
        delete pool[event][handler];
        return;
      }
      if (typeof handler === 'undefined') {
        Object.keys(pool[event]).forEach((key) => {
          delete pool[event][key][fieldName];
          delete pool[event][key];
        });
        delete pool[event];
      }
    };
    this.$emit = async (event, params) => {
      return await new Promise((resolve) => {
        if (!isEventExist(event)) {
          console.warn(`vBus $emit: event name ${event} is not exist`);
          return resolve();
        }
        const results = Object.keys(pool[event]).map((key) => {
          const { ctx, handler } = pool[event][key];
          return ctx !== undefined ? handler.call(ctx, params) : handler(params);
        });
        const asyncResults = results.filter((item) => {
          return item instanceof Promise;
        });
        if (asyncResults && asyncResults.length) {
          const syncResults = results.filter((item) => {
            return !(item instanceof Promise);
          });
          const done = (res) => {
            res = [].concat(syncResults, res);
            res = res.length > 1 ? res : res[0];
            resolve(res);
          };
          Promise.all(asyncResults).then(done).catch(done);
        } else {
          resolve(results.length > 1 ? results : results[0]);
        }
      });
    };
  }
}
export default new VBus();
